// Copyright (c) 2012 SameGoal LLC.
// All Rights Reserved.
// Author: Andy Hochhaus <ahochhaus@samegoal.com>

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "net/http/server/event/request.h"

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <re2/re2.h>

#include <map>
#include <string>
#include <vector>

#include "base/string.h"
#include "net/http/server/event/connection.h"
#include "third_party/zlib/gzip.h"

const uint32 kMinGzipSize = 250;

// Most comments in this file come from the HTTP 1.1 spec:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html

namespace sg {
namespace net {
namespace http {
namespace server {
namespace event {

Request::Request(Connection *conn)
    : conn_(conn), gzip_stream_(NULL), browser_supports_gzip_(false),
      request_state_(REQUEST), request_type_(GET), request_version_(ONE),
      post_parsed_(false) {
}

Request::~Request() {
  while (!response_headers_.empty()) {
    auto response_header = response_headers_.back();
    response_headers_.pop_back();
    delete response_header;
  }
  while (!headers_.empty()) {
    auto header = headers_.back();
    headers_.pop_back();
    delete header;
  }
  delete gzip_stream_;
}

bool Request::AddHeader(const std::string &str) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  raw_request_.push_back(str);
  switch (request_state_) {
    case REQUEST:
      // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
      {
        if (str.empty()) {
          // In the interest of robustness, servers SHOULD ignore any empty
          // line(s) received where a Request-Line is expected. In other words,
          // if the server is reading the protocol stream at the beginning of a
          // message and receives a CRLF first, it should ignore the CRLF.
          return true;
        }
        const size_t first_space = str.find(" ");
        const size_t last_space = str.rfind(" ");
        if (first_space == std::string::npos ||
            last_space == std::string::npos ||
            first_space == last_space) {
          LOG(ERROR) << "bad request line: " << str;
          return false;
        }
        const std::string method = str.substr(0, first_space);
        if (!ParseRequestType(method, &request_type_)) {
          LOG(INFO) << "unsupported http request type: " << method;
          return false;
        }
        request_uri_ = str.substr(first_space + 1,
                                  last_space - first_space - 1);
        const size_t first_quest_loc = request_uri_.find("?");
        if (first_quest_loc == std::string::npos) {
          request_path_ = request_uri_;
        } else {
          request_path_ = request_uri_.substr(0, first_quest_loc);
          request_query_ = request_uri_.substr(first_quest_loc + 1);
          ParseQueryString(request_query_, &query_params_);
        }
        const std::string version = str.substr(last_space + 1);
        if (!ParseRequestVersion(version, &request_version_)) {
          LOG(INFO) << "unsupported http version: " << version;
          return false;
        }
        request_state_ = HEADERS;
      }
      break;

    case HEADERS:
      // message-header = field-name ":" [ field-value ]
      // field-name     = token
      // field-value    = *( field-content | LWS )
      // field-content  = <the OCTETs making up the field-value
      //                  and consisting of either *TEXT or combinations
      //                  of token, separators, and quoted-string>
      if (str.empty()) {
        request_state_ = BODY;

        // TODO(ahochhaus): add iframe based gzip detection support as
        // described in Ch 9 of Even Faster Websites.
        std::string accept_encoding;
        GetHeader("Accept-Encoding", &accept_encoding);
        browser_supports_gzip_ =
            accept_encoding.find("gzip") != std::string::npos;
        break;
      } else {
        const size_t first_colon = str.find(":");
        if (first_colon == std::string::npos) {
          LOG(ERROR) << "bad header line: " << str;
          return false;
        }
        std::string key = str.substr(0, first_colon);
        sg::string::Strip(&key);
        // Field names are case-insensitive.
        NormalizeHttpHeader(&key);
        std::string value = str.substr(first_colon + 1);
        sg::string::Strip(&value);
        headers_.push_back(new KeyValuePair(key, value));

        if (key == "expect" && value == "100-continue") {
          // Expect must be processed incrementally to avoid the client
          // potentially blocking the remained of the request.
          output_buffer_.push_back("HTTP/1.1 100 Continue\r\n\r\n");
          conn_->OutputReadyEvent(this);
        }
      }
      break;

    default:
      LOG(ERROR) << "unknown request state: " << request_state_;
      return false;
  }
  return true;
}

void Request::AppendBody(const std::string &str, const bool body_done) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  request_body_.append(str);
  if (body_done) {
    request_state_ = RESPONSE;
  }
}

bool Request::IsReadingHeaders() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  if (request_state_ == REQUEST || request_state_ == HEADERS) {
    return true;
  }
  return false;
}

bool Request::IsReadingBody() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  if (request_state_ == BODY) {
    return true;
  }
  return false;
}

bool Request::IsReadingDone() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  if (request_state_ == RESPONSE || request_state_ == FINISHED) {
    return true;
  }
  return false;
}

bool Request::IsFinished() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  if (request_state_ == FINISHED) {
    return true;
  }
  return false;
}

bool Request::IsEncrypted() {
  return conn_->encrypted();
}

std::string Request::address() {
  return conn_->address();
}

uint16 Request::port() {
  return conn_->port();
}

Request::RequestType Request::request_type() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return request_type_;
}

std::string Request::request_uri() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return request_uri_;
}

std::string Request::request_path() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return request_path_;
}

std::string Request::request_query() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return request_query_;
}

std::string Request::request_body() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return request_body_;
}

Request::RequestVersion Request::request_version() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return request_version_;
}

bool Request::GetQueryParam(const std::string &key, std::string *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return GetFirstValue(key, &query_params_, value);
}

bool Request::GetQueryParamAsInt64(const std::string &key, int64 *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return GetFirstValueAsInt64(key, &query_params_, value);
}

bool Request::GetQueryParamAsUint64(const std::string &key, uint64 *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return GetFirstValueAsUint64(key, &query_params_, value);
}

Request::OrderedKeyValuePairs Request::GetQueryParamQueue() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return query_params_;
}

bool Request::GetHeader(const std::string &key, std::string *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string key_normalized = key;
  NormalizeHttpHeader(&key_normalized);
  return GetFirstValue(key_normalized, &headers_, value);
}

bool Request::GetHeaderAsInt64(const std::string &key, int64 *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string key_normalized = key;
  NormalizeHttpHeader(&key_normalized);
  return GetFirstValueAsInt64(key_normalized, &headers_, value);
}

bool Request::GetHeaderAsUint64(const std::string &key, uint64 *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string key_normalized = key;
  NormalizeHttpHeader(&key_normalized);
  return GetFirstValueAsUint64(key_normalized, &headers_, value);
}

Request::OrderedKeyValuePairs Request::GetHeaderQueue() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  return headers_;
}

bool Request::GetPost(const std::string &key, std::string *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EnsurePostParsed();
  return GetFirstValue(key, &post_, value);
}

bool Request::GetPostAsInt64(const std::string &key, int64 *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EnsurePostParsed();
  return GetFirstValueAsInt64(key, &post_, value);
}

bool Request::GetPostAsUint64(const std::string &key, uint64 *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EnsurePostParsed();
  return GetFirstValueAsUint64(key, &post_, value);
}

Request::OrderedKeyValuePairs Request::GetPostQueue() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EnsurePostParsed();
  return post_;
}

void Request::GetOutput(std::string *output) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  sg::string::Join("", output_buffer_, output);
  output_buffer_.clear();
}

std::string Request::GetRawRequest() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string http_header_str;
  sg::string::Join("\r\n", raw_request_, &http_header_str);
  return http_header_str + "\r\n" + request_body_;
}

void Request::AddResponseHeader(const std::string &key,
                                const std::string &value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string key_normalized = key;
  NormalizeHttpHeader(&key_normalized);
  response_headers_.push_back(new KeyValuePair(key_normalized, value));
}

void Request::AddResponseHeader(const std::string &key,
                                const int64 value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  char buf[24];  // ceiling(64/3) + sign char + NULL
  const size_t value_chars = snprintf(buf, sizeof(buf), "%ld", value);
  if (value_chars <= 0 || value_chars >= sizeof(buf)) {
    LOG(ERROR) << "unable to convert header value: " << value;
  }
  std::string key_normalized = key;
  NormalizeHttpHeader(&key_normalized);
  response_headers_.push_back(new KeyValuePair(key_normalized,
                                               std::string(buf)));
}

bool Request::GetResponseHeader(const std::string &key, std::string *value) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string key_normalized = key;
  NormalizeHttpHeader(&key_normalized);
  return GetFirstValue(key_normalized, &response_headers_, value);
}

void Request::AddDefaultResponseHeaders() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EventServer *es = conn_->event_server();
  headers::Helper *helper = es->headers_helper();
  if (!helper) {
    return;
  }
  // TODO(ahochhaus): Pass user agent
  auto default_headers = helper->DefaultHeaders("");
  for (auto i = default_headers.begin(); i != default_headers.end(); ++i) {
    AddResponseHeader(i->first, i->second);
  }
}

void Request::AddCacheableResponseHeaders() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EventServer *es = conn_->event_server();
  headers::Helper *helper = es->headers_helper();
  if (!helper) {
    return;
  }
  // TODO(ahochhaus): Pass user agent
  auto cacheable_headers = helper->CacheableHeaders("");
  for (auto i = cacheable_headers.begin(); i != cacheable_headers.end(); ++i) {
    AddResponseHeader(i->first, i->second);
  }
}

void Request::AddNonCacheableResponseHeaders() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  EventServer *es = conn_->event_server();
  headers::Helper *helper = es->headers_helper();
  if (!helper) {
    return;
  }
  // TODO(ahochhaus): Pass user agent
  auto non_cacheable_headers = helper->NonCacheableHeaders("");
  for (auto i = non_cacheable_headers.begin();
       i != non_cacheable_headers.end(); ++i) {
    AddResponseHeader(i->first, i->second);
  }
}

void Request::StartChunk(const HttpStatus code) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  if (browser_supports_gzip_) {
    InitGzipStream();
    AddResponseHeader("Content-Encoding", "gzip");
  }
  AddResponseHeader("Vary", "accept-encoding");
  AddResponseHeader("Transfer-Encoding", "chunked");
  // TODO(ahochhaus): verify that HTTP/1.0 can support chunked replies with a
  // Content-Length of 0. (https://github.com/ellzey/libevhtp/pull/24)
  if (request_version_ == ONE) {
    AddResponseHeader("Content-Length", 0);
  }
  OutputHeaders(code);
  conn_->OutputReadyEvent(this);
}

void Request::SendChunk(const std::string &body) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string chunk = body;
  if (browser_supports_gzip_) {
    CHECK_NOTNULL(gzip_stream_);
    gzip_stream_->Chunk(&chunk);
  }
  output_buffer_.push_back(
      sg::string::ToHexString(chunk.length()) + "\r\n" + chunk + "\r\n");
  conn_->OutputReadyEvent(this);
}

void Request::EndChunk(const std::string &body) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  std::string last_chunk = body;
  if (browser_supports_gzip_) {
    CHECK_NOTNULL(gzip_stream_);
    gzip_stream_->FinalChunk(&last_chunk);
  }
  if (!last_chunk.empty()) {
    output_buffer_.push_back(sg::string::ToHexString(last_chunk.length()) +
                             "\r\n" + last_chunk + "\r\n");
  }
  output_buffer_.push_back("0\r\n\r\n");
  request_state_ = FINISHED;
  conn_->OutputReadyEvent(this);
}

void Request::Send(const HttpStatus code, const std::string &body) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());

  std::string content_type;
  GetResponseHeader("Content-Type", &content_type);
  const bool is_format_uncompressed =
      (content_type.find("text/html") != std::string::npos ||
       content_type.find("text/plain") != std::string::npos ||
       content_type.find("text/css") != std::string::npos ||
       content_type.find("text/javascript") != std::string::npos);

  const bool gzip_candidate =
      (body.length() >= kMinGzipSize && is_format_uncompressed);
  const bool will_gzip = browser_supports_gzip_ && gzip_candidate;

  if (will_gzip) {
    AddResponseHeader("Content-Encoding", "gzip");
  }
  if (gzip_candidate) {
    // Send the vary header if the server would send a gzipped reply to the
    // client (if the client supported it). This is necessary as a proxy can
    // cache a reply to a client without gzip support.
    AddResponseHeader("Vary", "accept-encoding");
  }

  std::string reply = body;
  if (will_gzip) {
    sg::third_party::zlib::Gzip(&reply);
  }

  AddResponseHeader("Content-Length", reply.length());
  OutputHeaders(code);
  output_buffer_.push_back(reply);
  request_state_ = FINISHED;
  conn_->OutputReadyEvent(this);
}

void Request::SendError(const HttpStatus code, const std::string &error) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  Send(code, error);
}

void Request::SendRaw(const std::string &response_str) {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  output_buffer_.push_back(response_str);
  request_state_ = FINISHED;
  conn_->OutputReadyEvent(this);
}

void Request::CloseConnectionAfterNextWrite() {
  std::lock_guard<std::recursive_mutex> conn_lock(*conn_->mutex());
  conn_->CloseConnectionAfterNextWrite();
}

Connection *Request::connection() {
  return conn_;
}

bool Request::ParseRequestType(const std::string &type_str,
                               RequestType *type) {
  if (type_str == "GET") {
    *type = GET;
  } else if (type_str == "HEAD") {
    *type = HEAD;
  } else if (type_str == "POST") {
    *type = POST;
  } else {
    return false;
  }
  return true;
}

bool Request::ParseRequestVersion(const std::string &version_str,
                                  RequestVersion *version) {
  if (version_str == "HTTP/1.0") {
    *version = ONE;
  } else if (version_str == "HTTP/1.1") {
    *version = ONE_ONE;
  } else {
    return false;
  }
  return true;
}

void Request::EnsurePostParsed() {
  if (post_parsed_) {
    return;
  }

  // check if it is a multipart or not. more info on multipart encoding:
  // http://www.ietf.org/rfc/rfc1867.txt
  // http://www.ietf.org/rfc/rfc2388.txt
  std::string content_type;
  GetHeader("Content-Type", &content_type);
  bool is_multipart =
      content_type.find("multipart/form-data") != std::string::npos;
  if (is_multipart) {
    const std::string kBoundary = "boundary=";
    auto start = content_type.find(kBoundary);
    if (start != std::string::npos) {
      start += kBoundary.size();
      auto end = content_type.find(";");
      const std::string boundary = content_type.substr(start, end - start);
      ParseMultiPart(request_body_, boundary, &post_);
    }
    // intentionally not parsing if boundary is missing
  } else {
    ParseQueryString(request_body_, &post_);
  }
  post_parsed_ = true;
}

void Request::ParseQueryString(const std::string &query_str,
                               OrderedKeyValuePairs *queue) {
  std::vector<std::string> kv_pair_vec;
  sg::string::Split(query_str, "&", 0, &kv_pair_vec);
  for (auto kv_pari_str = kv_pair_vec.begin();
       kv_pari_str != kv_pair_vec.end(); ++kv_pari_str) {
    std::vector<std::string> key_value_vec;
    sg::string::Split(*kv_pari_str, "=", 1, &key_value_vec);
    UnescapeQueryPiece(&(key_value_vec[0]));
    if (key_value_vec.size() == 1) {
      queue->push_back(new KeyValuePair(key_value_vec[0], ""));
    } else {
      UnescapeQueryPiece(&(key_value_vec[1]));
      queue->push_back(new KeyValuePair(key_value_vec[0], key_value_vec[1]));
    }
  }
}

// This function parses multipart encoding which looks like the following,
// with "----WebKitFormBoundaryFuY5NKNxT7UryDr3" being the boundary.
//
// ------WebKitFormBoundaryFuY5NKNxT7UryDr3
// Content-Disposition: form-data; name="text_field"
//
// asdfadf
// ------WebKitFormBoundaryFuY5NKNxT7UryDr3
// Content-Disposition: form-data; name="file_field"; filename="file"
// Content-Type: application/octet-stream
//
// [data]
// ------WebKitFormBoundaryFuY5NKNxT7UryDr3--
void Request::ParseMultiPart(const std::string &data,
                             const std::string &boundary,
                             OrderedKeyValuePairs *queue) {
  const std::string kCrLf = "\r\n";
  const std::string delimiter = kCrLf + "--" + boundary;
  auto last = data.find(delimiter + "--");
  if (last == std::string::npos) {
    return;
  }
  // start pass the first delimiter
  auto start = delimiter.size();

  while (start < last) {
    auto end = data.find(delimiter, start);
    if (end == std::string::npos) {
      break;
    }
    const std::string part = data.substr(start, end - start);
    start = end + delimiter.size() + kCrLf.size();

    // parse the part
    const std::string kName = "name=\"";
    auto name_start = part.find(kName);
    if (name_start == std::string::npos) {
      continue;
    }
    name_start += kName.size();
    auto name_end = part.find("\"", name_start);
    std::string key = part.substr(name_start, name_end - name_start);

    // if the filename is populated, the key will look like this
    // field_name:file_name
    const std::string kFileName = "filename=\"";
    auto file_name_start = part.find(kFileName);
    if (file_name_start != std::string::npos) {
      file_name_start += kFileName.size();
      auto file_name_end = part.find("\"", file_name_start);
      key += ":" + part.substr(file_name_start,
                               file_name_end - file_name_start);
    }

    auto payload_start = part.find(kCrLf + kCrLf);
    if (payload_start == std::string::npos) {
      continue;
    }
    payload_start += kCrLf.size() + kCrLf.size();
    queue->push_back(new KeyValuePair(
        key, part.substr(payload_start, end - payload_start)));
  }
}

void Request::UnescapeQueryPiece(std::string *piece) {
  uint64 read_loc = 0, write_loc = 0;
  while (read_loc < piece->length()) {
    const char c = piece->at(read_loc++);
    switch (c) {
      case '+':
        (*piece)[write_loc++] = ' ';
        break;
      case '%':
        if (read_loc + 1 < piece->length()) {
          std::string hex_str;
          hex_str.push_back(piece->at(read_loc));
          hex_str.push_back(piece->at(read_loc + 1));
          uint32 char_val;
          if (sg::string::FromHexString(hex_str, &char_val)) {
            (*piece)[write_loc++] = static_cast<uint8> (char_val);
            read_loc += 2;
            break;
          }
          // fall through
        }
        // fall through
      default:
        (*piece)[write_loc++] = c;
    }
  }
  piece->resize(write_loc);
}

bool Request::GetFirstValue(const std::string &key,
                            OrderedKeyValuePairs *queue,
                            std::string *value) {
  for (auto i = queue->begin(); i != queue->end(); ++i) {
    if ((*i)->first == key) {
      *value = (*i)->second;
      return true;
    }
  }
  return false;
}

bool Request::GetFirstValueAsInt64(const std::string &key,
                                   OrderedKeyValuePairs *queue,
                                   int64 *value) {
  std::string value_str;
  if (!GetFirstValue(key, queue, &value_str)) {
    return false;
  }
  return sg::string::FromString(value_str, value);
}

bool Request::GetFirstValueAsUint64(const std::string &key,
                                    OrderedKeyValuePairs *queue,
                                    uint64 *value) {
  std::string value_str;
  if (!GetFirstValue(key, queue, &value_str)) {
    return false;
  }
  return sg::string::FromString(value_str, value);
}

void Request::NormalizeHttpHeader(std::string *key) {
  bool cap_next = true;
  const uint8 cap_offset = 'a' - 'A';
  const uint8 lower_offset = 'A' - 'a';
  for (uint64 i = 0; i < key->length(); ++i) {
    const char c = key->at(i);
    if (cap_next) {
      if (c >= 'a' && c <= 'z') {
        (*key)[i] = c - cap_offset;
      }
      cap_next = false;
    } else if (c == '-') {
      cap_next = true;
    } else {
      if (c >= 'A' && c <= 'Z') {
        (*key)[i] = c - lower_offset;
      }
    }
  }

  // special case http header capitalization
  if (*key == "X-Ua-Compatible") {
    *key = "X-UA-Compatible";
  } else if (*key == "X-Xss-Protection") {
    *key = "X-XSS-Protection";
  }
}

void Request::OutputHeaders(const HttpStatus code) {
  std::string connection;
  GetHeader("Connection", &connection);
  if (connection == "Keep-Alive" ||
      (connection != "Close" && request_version_ == Request::ONE_ONE)) {
    AddResponseHeader("Connection", "Keep-Alive");
  } else {
    AddResponseHeader("Connection", "Close");
  }

  std::vector<std::string> output;
  std::string response_line = "HTTP/1.1 ";
  if (request_version_ == ONE) {
    response_line = "HTTP/1.0 ";
  }
  response_line += sg::string::ToString(code);
  response_line += " ";
  response_line += StatusString(code);
  output.push_back(response_line);

  // convert headers to wire format
  for (auto i = response_headers_.begin(); i != response_headers_.end();
       ++i) {
    output.push_back((*i)->first + ": " + (*i)->second);
  }
  std::string header_str;
  sg::string::Join("\r\n", output, &header_str);
  output_buffer_.push_back(header_str + "\r\n\r\n");
  // Don't send the connection an OutputReadyEvent so that the response headers
  // and body are be included in a single trip.
}

std::string Request::StatusString(const HttpStatus code) {
  switch (code) {
    case OK:
      return "OK";
    case MOVED_PERMANENTLY:
      return "Moved Permanently";
    case FOUND:
      return "Found";
    case NOT_FOUND:
      return "Not Found";
    case INTERNAL_SERVER_ERROR:
      return "Internal Server Error";
    default:
      LOG(ERROR) << "Unknown http status code: " << code;
      return "Unknown";
  }
}

void Request::InitGzipStream() {
  gzip_stream_ = new zlib::GzipStream;
  gzip_stream_->Init();
}

}  // namespace event
}  // namespace server
}  // namespace http
}  // namespace net
}  // namespace sg
